package com.onpositive.data.serializer.readers;

import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
import java.util.LinkedHashSet;

import javaewah.EWAHCompressedBitmap;
import javaewah.IntIterator;

import com.onpositive.data.AbstractReader;
import com.onpositive.data.FilterTester;
import com.onpositive.data.IWritable;
import com.onpositive.data.serializer.writers.AbstractWriter;
import com.onpositive.data.serializer.writers.NumericValueCollectionWriter;

public class NumericValuesCollectionReader extends AbstractReader<Double>
		implements IWritable {

	private double[] valuesArray;
	private int recSize = 0, size, precision;
	private BitSet emptyCells;
	private double minValue, shift, roundingMult0, roundingMult1;
	boolean isInteger;

	public NumericValuesCollectionReader() {
		super(Double.class);
	}

	protected void doInit() {

		int initialReadSize = 4 * INT_SIZE + 2 * DOUBLE_SIZE;

		try {
			this.file.seek(collectionStart + myStart + INT_SIZE); // also skip
																	// writerID

			byte[] byteArr = new byte[initialReadSize];

			file.read(byteArr, 0, initialReadSize);

			ByteBuffer bBuf = ByteBuffer.wrap(byteArr);
			IntBuffer iBuf = bBuf.asIntBuffer();

			ByteBuffer bBuf2 = ByteBuffer.wrap(byteArr, 12, 16);
			DoubleBuffer dBuf = bBuf2.asDoubleBuffer();

			size = iBuf.get();
			int storageType = iBuf.get();

			precision = iBuf.get();

			minValue = dBuf.get();
			shift = dBuf.get();

			iBuf.position(7);

			EWAHCompressedBitmap emptyCellsBitMap = new EWAHCompressedBitmap();
			emptyCellsBitMap.deserialize(file);

			long t0 = System.currentTimeMillis();
			emptyCells = new BitSet(emptyCellsBitMap.cardinality());

			for (IntIterator it = emptyCellsBitMap.intIterator(); it.hasNext();) {
				int ind = it.next();
				emptyCells.set(ind);
			}

			long t1 = System.currentTimeMillis();
			super.bitmapInitTime = t1 - t0;
			if (storageType==NumericValueCollectionWriter.DOUBLE_STORE){
				recSize=8;
			}
			if ((storageType & NumericValueCollectionWriter.ONE_BYTE_PER_RECORD_STORE) != 0)
				recSize = 1;
			if ((storageType & NumericValueCollectionWriter.TWO_BYTE_PER_RECORD_STORE) != 0)
				recSize = 2;

			isInteger = ((storageType & NumericValueCollectionWriter.INTEGER_STORAGE) != 0);

			roundingMult0 = Math.pow(10, -precision);
			roundingMult1 = Math.pow(10, precision);
			if (roundingMult1 > 1)
				roundingMult1 = (int) (roundingMult1 + epsilon);

			int dataSize = (size - emptyCellsBitMap.cardinality()) * recSize;
			byte[] byteData = new byte[dataSize];
			file.read(byteData, 0, dataSize);

			t0 = System.currentTimeMillis();
			initValuesAray(byteData);
			t1 = System.currentTimeMillis();
			super.valuesArrayInitTime = t1 - t0;

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	protected void initValuesAray(byte[] byteData) {

		valuesArray = new double[size];

		ByteBuffer bBuf = ByteBuffer.wrap(byteData);
		
		boolean continueIteration = true;
		int start = 0;
		int end = emptyCells.nextSetBit(0);
		if (end == -1) {
			end = size;
		}
		if (recSize == 1)
			for (; continueIteration;) {
				for (int i = start; i < end; i++) {
					int iVal = (int) bBuf.get();
					int minVal = (int) Byte.MIN_VALUE;
					double val = minValue + shift * (iVal - minVal);
					valuesArray[i] = round(val);
				}

				if (end < size)
					valuesArray[end] = 0;

				start = end + 1;
				end = emptyCells.nextSetBit(start);
				end = end >= 0 ? end : size;
				continueIteration = start <= size;
			}

		if (recSize == 2) {
			CharBuffer cBuf = bBuf.asCharBuffer();
			for (; continueIteration;) {
				for (int i = start; i < end; i++) {
					double val = minValue + shift
							* ((int) cBuf.get() - Character.MIN_VALUE);
					valuesArray[i] = round(val);
				}

				if (end < size)
					valuesArray[end] = 0;

				start = end + 1;
				end = emptyCells.nextSetBit(start);
				end = end >= 0 ? end : size;
				continueIteration = start <= size;
			}
		}
		if (recSize ==8) {
			DoubleBuffer cBuf = bBuf.asDoubleBuffer();
			for (; continueIteration;) {
				for (int i = start; i < end; i++) {
					double val = cBuf.get();
					valuesArray[i] = round(val);
				}

				if (end < size)
					valuesArray[end] = 0;

				start = end + 1;
				end = emptyCells.nextSetBit(start);
				end = end >= 0 ? end : size;
				continueIteration = start <= size;
			}
		}
	}

	@Override
	public Double getObject(int i) {

		if (!isInitialized())
			init();

		if (emptyCells.get(i))
			return null;

		return doGet(i);
	}

	protected Double doGet(int i) {
		return valuesArray[i];
	}

	protected final boolean isInitialized() {
		return super.isInitialized();
	}

	private static final double epsilon = 0.0001;

	double round(double val) {
		int v1 = (int) (val * roundingMult0 + epsilon + 0.5);
		double v2 = v1 * roundingMult1;
		return v2;
	}

	@Override
	public void filterOut(LinkedHashSet<Object> obj, String text) {

	}

	public boolean hasValue(int po) {
		if (!isInitialized()) {
			init();
		}
		return !emptyCells.get(po);
	}

	@Override
	public boolean accept(int op, int a, Object constraint) {
		if (!isInitialized())
			init();
		boolean b = emptyCells.get(a);
		if (constraint == null) {
			switch (op) {
			case FilterTester.GE:
			case FilterTester.LE:
			case FilterTester.EQ:
			case FilterTester.CONTAINS:
				return b;


			case FilterTester.NEQ:
				return !b;

			default:
				break;
			}
			return false;
		}
		if (b) {
			return false;
		}

		double i = (valuesArray)[a];

		Number m = (Number) constraint;
		double ww = m.doubleValue();
		switch (op) {
		case FilterTester.EQ:
			return ww == i;
		case FilterTester.NEQ:
			return ww != i;
		case FilterTester.LE:
			return ww >= i;
		case FilterTester.LT:
			return ww > i;
		case FilterTester.GT:
			return ww < i;
		case FilterTester.GE:
			return ww <= i;
		case FilterTester.CONTAINS:
			return ww == i;
		default:
			throw new UnsupportedOperationException();
		}
	}

	public final double getDouble(int a) {
		if (!isInitialized()) {
			init();
		}
		double i = (valuesArray)[a];
		return i;
	}

	@Override
	public void setValue(int index, Object value) {
		if (!isInitialized()){
			init();
		}
		if (value==null){
			emptyCells.set(index);
			return;
		}
		else{
			emptyCells.clear(index);
			valuesArray[index]=((Number) value).doubleValue();
		}		
	}

	

	@Override
	public void serialize(DataOutputStream ds) throws IOException {
		if (!isInitialized()) {
			init();
		}
		NumericValueCollectionWriter we = new NumericValueCollectionWriter();
		for (int a = 0; a < valuesArray.length; a++) {
			if(hasValue(a)){
				we.append(valuesArray[a]);
			}
			else{
				we.append(null);
			}
		}
		we.serialize(ds);
	}

	public int size() {
		return valuesArray.length;
	}

	@Override
	public Object aggregate(int op, BitSet ss) {
		
		return new DoubleAggregator(op).getAggregatedValue(valuesArray, ss, emptyCells);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public AbstractWriter<Double> getWriter() {
		return (AbstractWriter)new NumericValueCollectionWriter();
	}

	@Override
	public void initEmpty(int dataSize) {
		emptyCells=new BitSet(dataSize);
		emptyCells.set(0, dataSize);
		valuesArray=new double[dataSize];
		setInitialized();
	}

	
	@Override
	public void expand(int newSize) {
		if (!isInitialized()){
			init();
		}
		double[] m=new double[newSize];
		System.arraycopy(valuesArray, 0, m, 0, valuesArray.length);
		emptyCells.set(valuesArray.length,newSize);
		valuesArray=m;
	}
}
